package com.zhx.common.chat.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Pair;
import com.zhx.common.chat.dao.*;
import com.zhx.common.chat.domain.dto.MsgReadInfoDTO;
import com.zhx.common.chat.domain.entity.*;
import com.zhx.common.chat.domain.enums.MessageMarkActTypeEnum;
import com.zhx.common.chat.domain.enums.MessageTypeEnum;
import com.zhx.common.chat.domain.vo.request.*;
import com.zhx.common.chat.domain.vo.request.member.MemberReq;
import com.zhx.common.chat.domain.vo.response.ChatMemberStatisticResp;
import com.zhx.common.chat.domain.vo.response.ChatMessageReadResp;
import com.zhx.common.chat.domain.vo.response.ChatMessageResp;
import com.zhx.common.chat.service.ChatService;
import com.zhx.common.chat.service.IContactService;
import com.zhx.common.chat.service.adapter.MemberAdapter;
import com.zhx.common.chat.service.adapter.MessageAdapter;
import com.zhx.common.chat.service.adapter.RoomAdapter;
import com.zhx.common.chat.service.cache.RoomCache;
import com.zhx.common.chat.service.cache.RoomGroupCache;
import com.zhx.common.chat.service.helper.ChatMemberHelper;
import com.zhx.common.chat.service.strategy.mark.AbstractMsgMarkStrategy;
import com.zhx.common.chat.service.strategy.mark.MsgMarkStrategyFactory;
import com.zhx.common.chat.service.strategy.msg.AbstractMsgHandler;
import com.zhx.common.chat.service.strategy.msg.MsgHandlerFactory;

import com.zhx.common.chat.service.strategy.msg.RecallMsgHandler;
import com.zhx.common.common.annotation.RedissonLock;
import com.zhx.common.common.domain.enums.NormalOrNoEnum;
import com.zhx.common.common.domain.vo.req.CursorPageBaseReq;
import com.zhx.common.common.domain.vo.resp.CursorPageBaseResp;
import com.zhx.common.common.event.MessageSendEvent;
import com.zhx.common.common.util.AssertUtil;
import com.zhx.common.user.dao.UserDao;
import com.zhx.common.user.domain.entity.User;
import com.zhx.common.user.domain.enums.ChatActiveStatusEnum;
import com.zhx.common.user.domain.enums.RoleEnum;
import com.zhx.common.user.service.IRoleService;
import com.zhx.common.user.service.cache.UserCache;
import com.zhx.common.websocket.domain.vo.resp.ChatMemberResp;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;


@Service
public class ChatServiceImpl implements ChatService {
    @Resource
    private RoomCache roomCache;
    @Resource
    private UserCache userCache;
    @Resource
    private RoomFriendDao roomFriendDao;
    @Resource
    private GroupMemberDao groupMemberDao;
    @Resource
    private RoomGroupCache roomGroupCache;
    @Resource
    private ApplicationEventPublisher applicationEventPublisher;
    @Resource
    private MessageDao messageDao;
    @Resource
    private MessageMarkDao messageMarkDao;
    @Resource
    private ContactDao contactDao;
    @Resource
    private IRoleService roleService;
    @Resource
    private RecallMsgHandler recallMsgHandler;
    @Resource
    private IContactService contactService;
    @Resource
    private UserDao userDao;
    @Resource
    private RoomGroupDao roomGroupDao;

    @Override
    @Transactional
    public Long sendMsg(ChatMessageReq request, Long uid) {
        check(request,uid);
        //通过不同的消息类型获得对应的消息处理器
        AbstractMsgHandler<?> strategy = MsgHandlerFactory.getStrategyNoNull(request.getMsgType());
        //检查并持久化消息
        Long messageId = strategy.checkAndSaveMsg(request, uid);
        //发布消息发送事件
        applicationEventPublisher.publishEvent(new MessageSendEvent(this, messageId));
        return messageId;
    }

    private void check(ChatMessageReq request, Long uid) {
        Room room = roomCache.get(request.getRoomId());
        //如果是热点群聊
        if(room.isHotRoom()){
            return;
        }
        //如果是单聊
        if(room.isRoomFriend()){
            RoomFriend roomFriend = roomFriendDao.getByRoomId(request.getRoomId());
            AssertUtil.equal(NormalOrNoEnum.NORMAL.getStatus(),roomFriend.getStatus(),"你已经被对方拉黑");
            AssertUtil.isTrue(uid.equals(roomFriend.getUid1()) || uid.equals(roomFriend.getUid2()),"你已经被对方拉黑");
        }
        //如果是群聊
        if(room.isRoomGroup()){
            RoomGroup roomGroup = roomGroupCache.get(request.getRoomId());
            GroupMember member = groupMemberDao.getMember(roomGroup.getId(), uid);
            AssertUtil.isNotEmpty(member, "您已经被移除该群");
        }
    }

    @Override
    public ChatMessageResp getMsgResp(Long msgId, Long receiveUid) {
        Message message = messageDao.getById(msgId);
        return getMsgResp(message, receiveUid);
    }

    @Override
    public ChatMessageResp getMsgResp(Message message, Long receiveUid) {
        return CollUtil.getFirst(getMsgRespBatch(Collections.singletonList(message), receiveUid));
    }

    /**
     * 批量获取消息的响应对象列表。
     *
     * @param messages 消息列表，不可为空。
     * @param receiveUid 接收消息的用户ID。
     * @return 返回一个 ChatMessageResp 对象的列表，代表处理后的消息响应。
     */
    private List<ChatMessageResp> getMsgRespBatch(List<Message> messages,Long receiveUid) {
        if(CollectionUtil.isEmpty(messages)){
            return new ArrayList<>();
        }
        //根据消息id批量查询消息标志
        List<MessageMark> msgMark = messageMarkDao.
                getValidMarkByMsgIdBatch(messages.stream().map(Message::getId).collect(Collectors.toList()));
        // 根据原始消息、消息标志和接收用户ID构建消息响应对象列表
        return MessageAdapter.buildMsgResp(messages, msgMark, receiveUid);
    }

    @Override
    public ChatMemberStatisticResp getMemberStatistic() {
        System.out.println(Thread.currentThread().getName());
        Long onlineNum = userCache.getOnlineNum();
//        Long offlineNum = userCache.getOfflineNum();不展示总人数
        ChatMemberStatisticResp resp = new ChatMemberStatisticResp();
        resp.setOnlineNum(onlineNum);
//        resp.setTotalNum(onlineNum + offlineNum);
        return resp;
    }

    @Override
    public CursorPageBaseResp<ChatMessageResp> getMsgPage(ChatMessagePageReq req, Long uid) {
        //用最后一条消息id，来限制被踢出的人能看见的最大一条消息
        Long lastMsgId = getLastMsgId(req.getRoomId(), uid);
        CursorPageBaseResp<Message> cursorPage = messageDao.getCursorPage(req.getRoomId(),req,lastMsgId);
        if(cursorPage.isEmpty()){
            return CursorPageBaseResp.empty();
        }
        return CursorPageBaseResp.init(cursorPage,getMsgRespBatch(cursorPage.getList(), uid));
    }

    @Override
    public void recallMsg(Long uid, ChatMessageBaseReq req) {
        Message message = messageDao.getById(req.getMsgId());
        checkRecall(uid,message);
        //执行消息撤回
        recallMsgHandler.recall(uid,message);
    }

    /**
     * 根据请求设置或取消消息标记。
     * 使用Redisson锁确保并发安全。函数首先根据请求获取相应的消息标记策略，然后根据请求的动词（标记或取消标记）
     * 调用相应的方法处理消息标记。
     *
     * @param uid 用户唯一标识，用于标识消息所属用户。
     * @param request 包含标记类型和操作类型的请求对象。其中，标记类型用于确定使用哪种策略处理消息标记，
     *                操作类型用于确定是标记消息还是取消标记消息。
     */
    @Override
    @RedissonLock(key = "#uid")
    public void setMsgMark(Long uid, ChatMessageMarkReq request) {
        // 获取消息标记策略对象，确保不为null，会根据消息标记类型获取对应的策略
        AbstractMsgMarkStrategy strategy = MsgMarkStrategyFactory.getStrategyNoNull(request.getMarkType());
        // 根据请求的操作类型执行相应的标记或取消标记操作
        switch (MessageMarkActTypeEnum.of(request.getActType())) {
            case MARK:
                // 对消息进行标记
                strategy.mark(uid, request.getMsgId());
                break;
            case UN_MARK:
                // 对消息进行取消标记
                strategy.unMark(uid, request.getMsgId());
                break;
        }
    }

    @Override
    public CursorPageBaseResp<ChatMessageReadResp> getReadPage(Long uid, ChatMessageReadReq request) {
        Message message = messageDao.getById(request.getMsgId());
        AssertUtil.isNotEmpty(message,"消息id有误");
        AssertUtil.equal(uid,message.getFromUid(),"只能查看自己的消息");
        CursorPageBaseResp<Contact> page;
        if(request.getSearchType() == 1){//查询已读列表
            page = contactDao.getReadPage(message,request);
        }else { //查询未读列表
            page = contactDao.getUnReadPage(message,request);
        }
        if(CollectionUtil.isEmpty(page.getList())){
            return CursorPageBaseResp.empty();
        }
        return CursorPageBaseResp.init(page, RoomAdapter.buildReadResp(page.getList()));
    }

    @Override
    public Collection<MsgReadInfoDTO> getMsgReadInfo(Long uid, ChatMessageReadInfoReq request) {
        List<Message> messages = messageDao.listByIds(request.getMsgIds());
        messages.forEach(message -> {
            AssertUtil.equal(uid,message.getFromUid(),"只能查询自己发送的消息");
        });
        return contactService.getMsgReadInfo(messages).values();
    }

    @Override
    @RedissonLock(key = "#uid")
    public void msgRead(Long uid, ChatMessageMemberReq request) {
        Contact contact = contactDao.get(uid, request.getRoomId());
        if (Objects.nonNull(contact)) {
            Contact update = new Contact();
            update.setId(contact.getId());
            update.setReadTime(new Date());
            contactDao.updateById(update);
        } else {
            Contact insert = new Contact();
            insert.setUid(uid);
            insert.setRoomId(request.getRoomId());
            insert.setReadTime(new Date());
            contactDao.save(insert);
        }
    }

    /**
     * 获取成员分页信息。
     *
     * @param request 包含请求参数的成员请求对象，如页面大小和游标位置。
     * @param memberUidList 成员UID列表，表示需要查询的用户ID集合。
     * @return CursorPageBaseResp<ChatMemberResp> 包含成员响应信息的分页响应对象，包括游标位置、是否为最后一页以及成员列表。
     */
    @Override
    public CursorPageBaseResp<ChatMemberResp> getMemberPage(MemberReq request, List<Long> memberUidList) {
        // 从请求的游标中解析出在线/离线状态和时间游标
        Pair<ChatActiveStatusEnum, String> pair = ChatMemberHelper.getCursorPair(request.getCursor());
        // 在线/离线
        ChatActiveStatusEnum activeStatusEnum = pair.getKey();
        // 时间游标 最后上下线时间
        String timeCursor = pair.getValue();

        List<ChatMemberResp> resultList = new ArrayList<>(); // 存储最终查询结果的列表
        Boolean isLast = Boolean.FALSE; // 标记是否为最后一页

        if (activeStatusEnum == ChatActiveStatusEnum.ONLINE) { // 处理在线成员列表请求
            // 查询在线成员
            CursorPageBaseResp<User> cursorPage = userDao.getCursorPage(memberUidList, new CursorPageBaseReq(request.getPageSize(), timeCursor), ChatActiveStatusEnum.ONLINE);
            resultList.addAll(MemberAdapter.buildMember(cursorPage.getList())); // 将查询到的在线成员添加到结果列表

            // 如果在线成员不能满足请求的页面大小，则补充离线成员
            if (cursorPage.getIsLast()) {
                activeStatusEnum = ChatActiveStatusEnum.OFFLINE;
                Integer leftSize = request.getPageSize() - cursorPage.getList().size();
                cursorPage = userDao.getCursorPage(memberUidList, new CursorPageBaseReq(leftSize, null), ChatActiveStatusEnum.OFFLINE);
                resultList.addAll(MemberAdapter.buildMember(cursorPage.getList())); // 将查询到的离线成员添加到结果列表
            }
            timeCursor = cursorPage.getCursor(); // 更新游标位置
            isLast = cursorPage.getIsLast(); // 更新是否为最后一页的标记
        } else if (activeStatusEnum == ChatActiveStatusEnum.OFFLINE) { // 处理离线成员列表请求
            // 直接查询离线成员
            CursorPageBaseResp<User> cursorPage = userDao.getCursorPage(memberUidList, new CursorPageBaseReq(request.getPageSize(), timeCursor), ChatActiveStatusEnum.OFFLINE);
            resultList.addAll(MemberAdapter.buildMember(cursorPage.getList())); // 将查询到的离线成员添加到结果列表
            timeCursor = cursorPage.getCursor(); // 更新游标位置
            isLast = cursorPage.getIsLast(); // 更新是否为最后一页的标记
        }

        // 获取群成员的角色ID
        List<Long> uidList = resultList.stream().map(ChatMemberResp::getUid).collect(Collectors.toList());
        RoomGroup roomGroup = roomGroupDao.getByRoomId(request.getRoomId());
        Map<Long, Integer> uidMapRole = groupMemberDao.getMemberMapRole(roomGroup.getId(), uidList);
        resultList.forEach(member -> member.setRoleId(uidMapRole.get(member.getUid()))); // 设置成员的角色ID

        // 组装并返回分页响应对象
        return new CursorPageBaseResp<>(ChatMemberHelper.generateCursor(activeStatusEnum, timeCursor), isLast, resultList);
    }

    private Long getLastMsgId(Long roomId,Long receiveUid){
        Room room = roomCache.get(roomId);
        AssertUtil.isNotEmpty(room,"房间号有误");
        if(room.isHotRoom()){
            return null;
        }
        AssertUtil.isNotEmpty(receiveUid, "请先登录");
        Contact contact = contactDao.get(receiveUid, roomId);
        return contact.getLastMsgId();
    }

    private void checkRecall(Long uid, Message message) {
        AssertUtil.isNotEmpty(message, "消息有误");
        AssertUtil.notEqual(message.getType(), MessageTypeEnum.RECALL.getType(), "消息无法撤回");
        boolean hasPower = roleService.hasPower(uid, RoleEnum.CHAT_MANAGE);
        if (hasPower) {
            return;
        }
        boolean self = Objects.equals(uid, message.getFromUid());
        AssertUtil.isTrue(self, "抱歉,您没有权限");
        long between = DateUtil.between(message.getCreateTime(), new Date(), DateUnit.MINUTE);
        AssertUtil.isTrue(between < 2, "覆水难收，超过2分钟的消息不能撤回哦~~");
    }
}
